package vn.lumi.smarthome.service;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo.DetailedState;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import vn.lumi.smarthome.LumiHome;
import vn.lumi.smarthome.io.model.Device;
import vn.lumi.smarthome.io.model.Floor;
import vn.lumi.smarthome.network.AllStatus;
import vn.lumi.smarthome.network.Config;
import vn.lumi.smarthome.network.HC_ACK;
import vn.lumi.smarthome.network.LoginFeedback;
import vn.lumi.smarthome.network.Reconnect;
import vn.lumi.smarthome.provider.SmartHomeProvider;

public class ReconnectThread extends Thread{
	private Handler serviceHandler;
	private Context mContext;
	private String TAG = "ReconnectThread";

	public ReconnectThread(Handler serviceHandler, Context context){
		this.serviceHandler = serviceHandler;
		this.mContext = context;
		Log.d("ReconnectThread","created");
	}
	
	@Override
	public void run() {
		Log.d("ReconnectThread","running");
		// Check Wifi, 3G
		boolean haveWifi = false;
		boolean have3G = false;
		ConnectivityManager connMgr = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);

		final android.net.NetworkInfo wifi = connMgr
				.getNetworkInfo(ConnectivityManager.TYPE_WIFI);

		final android.net.NetworkInfo mobile = connMgr
				.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);

		if (wifi != null && wifi.isAvailable()
				&& wifi.getDetailedState() == DetailedState.CONNECTED) {
			haveWifi = true;
		}

		if (mobile != null && mobile.isAvailable()
				&& mobile.getDetailedState() == DetailedState.CONNECTED) {
			have3G = true;
		}
		
		if(haveWifi == false && have3G == false){
			//send msg to Service
			Log.d(TAG,"No network, send msg to Service");
			serviceHandler.sendEmptyMessage(SmartHomeService.RECON2SER_FAILED_NONETWORK);
			Thread.currentThread().interrupt();
			return;
		}
		
		boolean haveLAN = false;
		boolean haveWAN = false;
		LoginFeedback loginFeedback = new LoginFeedback();
		ArrayList<Device> devices = new ArrayList<Device>();
		ArrayList<Floor> floors = new ArrayList<Floor>();
		if (haveWifi == true) {
			// open socket to LAN IP
			haveLAN = TryReconnect("LAN", loginFeedback);
		}

		// if haveLAN = false or mobile is using 3G --> check WAN
		if (haveLAN == false) {
			haveWAN = TryReconnect("WAN", loginFeedback);
		}
		

		if (haveLAN == true || haveWAN == true) { 
			//save parameters
			LumiHome.Uid = loginFeedback.getUid();
			LumiHome.Session_id = loginFeedback.getSid();
			if(haveLAN == true) LumiHome.Timeout = 3000; //ms
			else if(haveWAN == true) LumiHome.Timeout = 6000; //ms;
			
			// tach ban tin Login Feedback
			if (loginFeedback.getUserValid() == LoginFeedback.LOGIN_SUCCESSFUL) {
				if (LumiHome.HC_Config_ver == loginFeedback.getHCConfigVer()) {
					// nhan ban tin device status
					if(GetStatusFromHC(devices)==false){
						//send msg to Service
						Log.d(TAG,"Can not get all status from HC");
						serviceHandler.sendEmptyMessage(SmartHomeService.RECON2SER_FAILED_CANNOT_CONNECT);
						Thread.currentThread().interrupt();
						return;
					}
					else{
						Log.d(TAG,"Updated all device status from HC");
//						SmartHomeProvider.insertDataStateFromHCToDB(mContext, devices);
						
					}
				} 
				else {
					// nhan ban tin config
					if(GetConfigFromHC(floors)==false){
						//send msg to Service
						Log.d(TAG,"Can not get config from HC");
						serviceHandler.sendEmptyMessage(SmartHomeService.RECON2SER_FAILED_CANNOT_CONNECT);
						Thread.currentThread().interrupt();
						return;
					}
					else{
						Log.d(TAG,"Got new config from HC, version="+loginFeedback.getHCConfigVer());
//						SmartHomeProvider.insertConfigFromHCToDB(mContext, devices);
//						LumiHome.HC_Config_ver = loginFeedback.getHCConfigVer();
					}
				}
				
				//send msg to service
				Message msg=Message.obtain();
				msg.what = SmartHomeService.RECON2SER_SUCESSFUL;
				serviceHandler.sendMessage(msg);
				
				//end thread
				Log.d(TAG,"Stopping");
				Thread.currentThread().interrupt();
			}
			//if session has been deleted --> inform Service to login again
			else if(loginFeedback.getUserValid() == LoginFeedback.SESSION_DELETED){
				//close socket 
				try {
					LumiHome.socket.close();
					LumiHome.is.close();
					LumiHome.os.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				Log.d(TAG,"Session deleted, informing Service, close socket, then stop");
				serviceHandler.sendEmptyMessage(SmartHomeService.RECON2SER_FAILED_SESSION_DELETED);
				Thread.currentThread().interrupt();
				return;
			}
		}
		
		else{
			//send msg to Service
			Log.d(TAG,"Can not get data from HC");
			serviceHandler.sendEmptyMessage(SmartHomeService.RECON2SER_FAILED_CANNOT_CONNECT);
			Thread.currentThread().interrupt();
			return;
		}
	}
	
	public boolean TryReconnect(String path, LoginFeedback loginFeedback) {
		int openSocketRetry = 0;
		Log.d(TAG, "Openning socket to " + path + " HC");
		boolean HCConnected = false;
		
		//Try open socket 3 times
		while (openSocketRetry < 2) {
			try {
				LumiHome.socket = new Socket();
				LumiHome.socket.setReceiveBufferSize(10000);
				if (path == "LAN") {
					LumiHome.socket.connect(new InetSocketAddress(LumiHome.LAN_ip, LumiHome.LAN_port),
							3000); // timeout = 1s
					LumiHome.socket.setSoTimeout(3000);
				} else if (path == "WAN") {
					LumiHome.socket.connect(
							new InetSocketAddress(LumiHome.DNS_address, LumiHome.WAN_port), 5000);
					LumiHome.socket.setSoTimeout(5000);
				}
				LumiHome.is = LumiHome.socket.getInputStream();
				LumiHome.os = LumiHome.socket.getOutputStream();
				HCConnected = true;
				Log.d("Socket", "Socket opened");
				break;
			} catch (Exception e) {
				Log.d(TAG, "Failed to open socket to " + path +  ", retry="
						+ (openSocketRetry + 1));
				e.printStackTrace();
				HCConnected = false;
				try {
					if(LumiHome.is != null) LumiHome.is.close();
					if(LumiHome.os != null) LumiHome.os.close();
					if(LumiHome.socket!=null) LumiHome.socket.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
				openSocketRetry++;
			}
		}

		//if socket is openned 
		if (HCConnected == true) {
			// format reconnect message to HC
			Reconnect recon = new Reconnect(LumiHome.Uid, LumiHome.Session_id, LumiHome.HC_Config_ver);
			byte[] dataout = recon.generateByteArray();
			
			//send and receive reconnect message
			try {
				//send reconnect to HC
				LumiHome.os.write(dataout);
				String log = "";
				for (byte b: dataout){
					 log += " " + String.format("%x", b);
					}
				Log.d("Socket", "Reconnect Sent: " + log); 

				//read 3 bytes from HC
				byte[] buffer_temp = new byte[1000];
				LumiHome.is.read(buffer_temp,0,3);
				
				int length = Byte2Unsigned(buffer_temp[1])*256 + Byte2Unsigned(buffer_temp[2]);
				//read remain bytes of message
				LumiHome.is.read(buffer_temp, 1, length);
			
				//copy to buffer
				byte[] buffer = new byte[length+1];
				for (int i = 0; i < length+1; i++) {
					buffer[i] = buffer_temp[i];
				}

				//log buffer
				log = "";
				for (byte b: buffer){
					 log += " " + String.format("%x", b);
					}
				Log.d("Socket", "Login Feedback: " + log); 
				
				//check login result
				if (loginFeedback.isCorrectFormat(buffer, buffer.length) == true) {
					HCConnected = true;
					loginFeedback.parseMessage(buffer, buffer.length);
				} else{
					HCConnected = false;
					Log.d("Socket", "Login Feedback is wrong!");
				}
			} catch (IOException e) {
				Log.d("Socket", "Failed to get reconnect reply from HC");
				e.printStackTrace();
				HCConnected = false;
			}
		}
		return HCConnected;
	}

	public boolean GetStatusFromHC(ArrayList<Device> devices){
		byte[] buffer_temp = new byte[1000];
		try {
			//get 3 first bytes
			LumiHome.is.read(buffer_temp,0,3);
			int length = Byte2Unsigned(buffer_temp[1])*256 + Byte2Unsigned(buffer_temp[2]);
			
			//get remaining bytes
			LumiHome.is.read(buffer_temp, 1, length);
	
			//save in buffer
			byte[] buffer = new byte[length+1];
			for (int i = 0; i < length+1; i++) {
				buffer[i] = buffer_temp[i];
			}
	
			//log
			String log = "";
			for (byte b: buffer){
				 log += " " + String.format("%x", b);
				}
			Log.d(TAG, "status: "+ "length = " + length +":" + log); 
			AllStatus deviceStatus = new AllStatus(mContext);
			deviceStatus.parseMessage(buffer, buffer.length);
			devices = deviceStatus.getDevices();
			return true;
		} catch(Exception e){
			try {
				LumiHome.socket.close();
				LumiHome.is.close();
				LumiHome.os.close();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			LumiHome.SocketState = LumiHome.SOCKET_CLOSED;
			e.printStackTrace();
			return false;
		}
	}
	
	private boolean GetConfigFromHC(ArrayList<Floor> floors){
		byte[] buffer = new byte[10000];
		int buffer_length = 0;
		try{
			for (int i = 0;;i++) {
				byte[] buffer_temp = new byte[1000];
				//read 3 first bytes
				LumiHome.is.read(buffer_temp,0,3);
				int length = Byte2Unsigned(buffer_temp[1])*256 + Byte2Unsigned(buffer_temp[2]);
				byte msgLength_high = buffer_temp[1];
				byte msgLength_low = buffer_temp[2];
				
				//read remaining bytes
				LumiHome.is.read(buffer_temp, 1, length);
				
				//log
				String len = "";
				len += "[" +String.format("%x", msgLength_high);
				len += " " +String.format("%x", msgLength_low) + "]";
				if(length > 0){
					String log = "";
					for (int i1 =0; i1<length+1; i1++){
						 log += " " + String.format("%x", buffer_temp[i1]);
						 if(i1 == 0) log+=len;
						}
					Log.d(TAG,"config: " + "length = " + length +":"+ log);
				}
				
				//check if msg is ack --> end message
				HC_ACK ack = new HC_ACK();
				if (ack.isCorrectFormat(buffer_temp, length+1) == true){
					Log.d(TAG,"ACK --> end receive config messages");	
					break;
				}
				
				//save in buffer
				if(i==0){
					for (int j = 0; j < length+1; j++) {
						buffer[j] = buffer_temp[j];
					}
					buffer_length = length +1;
				}
				else{
					for(int j=0; j<length; j++){
						buffer[buffer_length + j] = buffer_temp[j+1];
					}
					buffer_length += length;
				}
			} 
		}catch(Exception e){
			try {
				LumiHome.socket.close();
				LumiHome.is.close();
				LumiHome.os.close();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			LumiHome.SocketState = LumiHome.SOCKET_CLOSED;
			e.printStackTrace();
			return false;
		}
		
		//floors
		Config cf = new Config();
		cf.parseMessage(buffer, buffer_length);
		floors.addAll(cf.getFloors());
		return true;
	}
	
	public int Byte2Unsigned(byte a){
		return a & 0xFF;
	}
	
}
